﻿using System;
using System.Collections.Generic;
using System.Text;
using RoughJs.Ast;
using System.Xml;
using System.IO;

namespace RoughJs.E4X
{
    public class XmlProcessor
    {
        private bool ignoreComments;
        private bool ignoreProcessingInstructions;
        private bool ignoreWhitespace;
        private bool prettyPrint;
        private int prettyIndent;

        //private transient javax.xml.parsers.DocumentBuilderFactory dom;
        //private transient javax.xml.transform.TransformerFactory xform;
        //private transient DocumentBuilder documentBuilder;
        //  private RhinoSAXErrorHandler errorHandler = new RhinoSAXErrorHandler();

        private void readObject(StreamReader stream)
        {
            //stream.defaultReadObject();
            //this.dom = javax.xml.parsers.DocumentBuilderFactory.newInstance();
            //this.dom.setNamespaceAware(true);
            //this.dom.setIgnoringComments(false);
            //this.xform = javax.xml.transform.TransformerFactory.newInstance();
        }

        public  class RhinoSAXErrorHandler
        {


            private void throwError(XmlException e)
            {
                throw ScriptRuntime.constructError("TypeError", e.Message,
                    e.LineNumber - 1);
            }

            public void error(XmlException e)
            {
                throwError(e);
            }

            public void fatalError(XmlException e)
            {
                throwError(e);
            }

            public void warning(XmlException e)
            {
                Context.reportWarning(e.Message);
            }
        }

        public XmlProcessor()
        {
            setDefault();
            //this.dom = javax.xml.parsers.DocumentBuilderFactory.newInstance();
            //this.dom.setNamespaceAware(true);
            //this.dom.setIgnoringComments(false);
            //this.xform = javax.xml.transform.TransformerFactory.newInstance();
        }

        public void setDefault()
        {
            this.setIgnoreComments(true);
            this.setIgnoreProcessingInstructions(true);
            this.setIgnoreWhitespace(true);
            this.setPrettyPrinting(true);
            this.setPrettyIndent(2);
        }

        public void setIgnoreComments(bool b)
        {
            this.ignoreComments = b;
        }

        public void setIgnoreWhitespace(bool b)
        {
            this.ignoreWhitespace = b;
        }

        public void setIgnoreProcessingInstructions(bool b)
        {
            this.ignoreProcessingInstructions = b;
        }

        public void setPrettyPrinting(bool b)
        {
            this.prettyPrint = b;
        }

        public void setPrettyIndent(int i)
        {
            this.prettyIndent = i;
        }

        public bool isIgnoreComments()
        {
            return ignoreComments;
        }

        public bool isIgnoreProcessingInstructions()
        {
            return ignoreProcessingInstructions;
        }

        public bool isIgnoreWhitespace()
        {
            return ignoreWhitespace;
        }

        public bool isPrettyPrinting()
        {
            return prettyPrint;
        }

        public int getPrettyIndent()
        {
            return prettyIndent;
        }

        private String toXmlNewlines(String rv)
        {
            StringBuilder nl = new StringBuilder();
            for (int i = 0; i < rv.Length; i++)
            {
                if (rv.ToCharArray()[i] == '\r')
                {
                    if (rv.ToCharArray()[i + 1] == '\n')
                    {

                    }
                    else
                    {
                        nl.Append('\n');
                    }
                }
                else
                {
                    nl.Append(rv.ToCharArray()[i]);
                }
            }
            return nl.ToString();
        }

        public XmlDocument newDocument()
        {
            //DocumentBuilder builder = null;
            try
            {

                //builder = getDocumentBuilderFromPool();
                //return builder.newDocument();
                return new XmlDocument();
            }
            catch (Exception ex)
            {

                throw (ex);
            }
            //finally
            //{
            //    if (builder != null)
            //        returnDocumentBuilderToPool(builder);
            //}
        }


        public String ecmaToXmlString(System.Xml.XmlNode node)
        {
            //    See ECMA 357 Section 10.2.1
            StringBuilder s = new StringBuilder();
            int indentLevel = 0;
            if (prettyPrint)
            {
                for (int i = 0; i < indentLevel; i++)
                {
                    s.Append(' ');
                }
            }
            if (node is XmlText)
            {
                String data = ((XmlText)node).Value;
                //    TODO Does Java trim() work same as XMLWhitespace?
                String v = (prettyPrint) ? data.Trim() : data;
                s.Append(escapeElementValue(v));
                return s.ToString();
            }
            if (node is XmlAttribute)
            {
                String value = ((XmlAttribute)node).Value;
                s.Append(escapeAttributeValue(value));
                return s.ToString();
            }
            if (node is XmlComment)
            {
                s.Append("<!--" + ((XmlComment)node).Value + "-->");
                return s.ToString();
            }
            if (node is XmlProcessingInstruction)
            {
                XmlProcessingInstruction pi = (XmlProcessingInstruction)node;
                s.Append("<?" + pi.Target + " " + pi.Value + "?>");
                return s.ToString();
            }
            s.Append(elementToXmlString((XmlElement)node));
            return s.ToString();
        }

        public String escapeAttributeValue(Object value)
        {
            String text = ScriptRuntime.toString(value);

            if (text.Length == 0) return "";

            XmlDocument dom = newDocument();
            XmlElement e = dom.CreateElement("a");
            e.SetAttribute("b", text);
            //XmlText textNode = dom.CreateTextNode(text);
            //e.AppendChild(textNode);
            String elementText = toString(e);
            int begin = elementText.IndexOf('"');
            int end = elementText.LastIndexOf('"');
            return elementText.Substring(begin + 1, end-(begin+1));
        }

        public String escapeTextValue(Object value)
        {
            if (value is XMLObjectImpl)
            {
                return ((XMLObjectImpl)value).toXMLString();
            }

            String text = ScriptRuntime.toString(value);

            if (text.Length == 0) return text;

            XmlDocument dom = newDocument();
            XmlElement e = dom.CreateElement("a");
            
            XmlText textNode = dom.CreateTextNode(text);
            e.AppendChild(textNode);
            String elementText = toString(e);

            int begin = elementText.IndexOf('>') + 1;
            int end = elementText.LastIndexOf('<');
            return (begin < end) ? elementText.Substring(begin, end - begin ) : "";
        }

        private String escapeElementValue(String s)
        {
            //    TODO    Check this
            return escapeTextValue(s);
        }
        private String elementToXmlString(XmlElement element)
        {
            //    TODO    My goodness ECMA is complicated (see 10.2.1).  We'll try this first.
            XmlElement copy = (XmlElement)element.CloneNode(true);
            if (prettyPrint)
            {
                beautifyElement(copy, 0);
            }
            return toString(copy);
        }

        private void beautifyElement(XmlElement e, int indent)
        {
            StringBuilder s = new StringBuilder();
            s.Append('\n');
            for (int i = 0; i < indent; i++)
            {
                s.Append(' ');
            }
            String afterContent = s.ToString();
            for (int i = 0; i < prettyIndent; i++)
            {
                s.Append(' ');
            }
            String beforeContent = s.ToString();

            //    We "mark" all the nodes first; if we tried to do this loop otherwise, it would behave unexpectedly (the inserted nodes
            //    would contribute to the length and it might never terminate).
            List<System.Xml.XmlNode> toIndent = new List<System.Xml.XmlNode>();
            bool indentChildren = false;
            for (int i = 0; i < e.ChildNodes.Count; i++)
            {
                if (i == 1) indentChildren = true;
                if (e.ChildNodes[i] is XmlText)
                {
                    toIndent.Add(e.ChildNodes[i]);
                }
                else
                {
                    indentChildren = true;
                    toIndent.Add(e.ChildNodes[i]);
                }
            }
            if (indentChildren)
            {
                for (int i = 0; i < toIndent.Count; i++)
                {
                    e.InsertBefore(e.OwnerDocument.CreateTextNode(beforeContent),
                            toIndent[i]);
                }
            }
            XmlNodeList nodes = e.ChildNodes;
            List<XmlElement> list = new List<XmlElement>();
            for (int i = 0; i < nodes.Count; i++)
            {
                if (nodes[i] is XmlElement)
                {
                    list.Add((XmlElement)nodes[i]);
                }
            }
            foreach (XmlElement elem in list)
            {
                beautifyElement(elem, indent + prettyIndent);
            }
            if (indentChildren)
            {
                e.AppendChild(e.OwnerDocument.CreateTextNode(afterContent));
            }
        }

        private String toString(System.Xml.XmlNode node)
        {
            return node.OuterXml;
        }

        private void addProcessingInstructionsTo(List<System.Xml.XmlNode> list, System.Xml.XmlNode node)
        {
        if (node is XmlProcessingInstruction) {
            list.Add(node);
        }
        if (node.ChildNodes != null) {
            for (int i=0; i<node.ChildNodes.Count; i++) {
                addProcessingInstructionsTo(list, node.ChildNodes[i]);
            }
        }
    }
        private void addCommentsTo(List<System.Xml.XmlNode> list, System.Xml.XmlNode node)
        {
        if (node is XmlComment) {
            list.Add(node);
        }
        if (node.ChildNodes != null) {
            for (int i=0; i<node.ChildNodes.Count; i++) {
                addProcessingInstructionsTo(list, node.ChildNodes[i]);
            }
        }
    }
        private void addTextNodesToRemoveAndTrim(List<System.Xml.XmlNode> toRemove, System.Xml.XmlNode node)
        {
        if (node is XmlText) {
            XmlText text = (XmlText)node;
            bool BUG_369394_IS_VALID = false;
            if (!BUG_369394_IS_VALID) {
                text.Value=text.Value.Trim();
            } else {
                if (text.Value.Trim().Length == 0) {
                    text.Value = "";
                }
            }
            if (text.Value.Length == 0) {
                toRemove.Add(node);
            }
        }
        if (node.ChildNodes != null) {
            for (int i=0; i<node.ChildNodes.Count; i++) {
                addTextNodesToRemoveAndTrim(toRemove, node.ChildNodes[i]);
            }
        }
    }
      public  System.Xml.XmlNode toXml(String defaultNamespaceUri, String xml) {
        //    See ECMA357 10.3.1
        //DocumentBuilder builder = null;
          try
          {
              String syntheticXml = "<parent xmlns=\"" + defaultNamespaceUri +
                  "\">" + xml + "</parent>";
              //builder = getDocumentBuilderFromPool(); 
              XmlDocument document = new XmlDocument();
              document.LoadXml(syntheticXml);
              if (ignoreProcessingInstructions)
              {
                  List<System.Xml.XmlNode> list = new List<System.Xml.XmlNode>();
                  addProcessingInstructionsTo(list, document);
                  foreach (System.Xml.XmlNode node in list)
                  {
                      node.ParentNode.RemoveChild(node);
                  }
              }
              if (ignoreComments)
              {
                  List<System.Xml.XmlNode> list = new List<System.Xml.XmlNode>();
                  addCommentsTo(list, document);
                  foreach (System.Xml.XmlNode node in list)
                  {
                      node.ParentNode.RemoveChild(node);
                  }
              }
              if (ignoreWhitespace)
              {
                  //    Apparently JAXP setIgnoringElementContentWhitespace() has a different meaning, it appears from the Javadoc
                  //    Refers to element-only content models, which means we would need to have a validating parser and DTD or schema
                  //    so that it would know which whitespace to ignore.

                  //    Instead we will try to delete it ourselves.
                  List<System.Xml.XmlNode> list = new List<System.Xml.XmlNode>();
                  addTextNodesToRemoveAndTrim(list, document);
                  foreach (System.Xml.XmlNode node in list)
                  {
                      node.ParentNode.RemoveChild(node);
                  }
              }
              XmlNodeList rv = document.DocumentElement.ChildNodes;
              if (rv.Count > 1)
              {
                  throw ScriptRuntime.constructError("SyntaxError", "XML objects may contain at most one node.");
              }
              else if (rv.Count == 0)
              {
                  System.Xml.XmlNode node = document.CreateTextNode("");
                  return node;
              }
              else
              {
                  System.Xml.XmlNode node = rv[0];
                  document.DocumentElement.RemoveChild(node);
                  return node;
              }
          }
          catch
          {
              throw new Exception("Unreachable.");
          }
        //} catch (java.io.IOException e) {
        //    
        //} catch (javax.xml.parsers.ParserConfigurationException e) {
        //    throw new RuntimeException(e);
        //} finally {
        //    if (builder != null)
        //        returnDocumentBuilderToPool(builder);
        //}
    }


        //private javax.xml.parsers.DocumentBuilderFactory getDomFactory() {
        //    return dom;
        //}

        //private synchronized DocumentBuilder getDocumentBuilderFromPool()
        //    throws javax.xml.parsers.ParserConfigurationException
        //{
        //    DocumentBuilder result;
        //    if (documentBuilder == null) {
        //        javax.xml.parsers.DocumentBuilderFactory factory = getDomFactory();
        //        result = factory.newDocumentBuilder();
        //    } else {
        //        result = documentBuilder;
        //        documentBuilder = null;
        //    }
        //    result.setErrorHandler(errorHandler);
        //    return result;
        //}

        //private synchronized void returnDocumentBuilderToPool(DocumentBuilder db) {
        //    if (documentBuilder == null) {
        //        try {
        //            db.reset();
        //            documentBuilder = db;
        //        } catch (UnsupportedOperationException e) {
        //            // document builders that don't support reset() can't
        //            // be pooled
        //        }
        //    }
        //}

        //private void addProcessingInstructionsTo(List<Node> list, Node node) {
        //    if (node is ProcessingInstruction) {
        //        list.add(node);
        //    }
        //    if (node.getChildNodes() != null) {
        //        for (int i=0; i<node.getChildNodes().getLength(); i++) {
        //            addProcessingInstructionsTo(list, node.getChildNodes().item(i));
        //        }
        //    }
        //}

        //private void addCommentsTo(List<Node> list, Node node) {
        //    if (node is Comment) {
        //        list.add(node);
        //    }
        //    if (node.getChildNodes() != null) {
        //        for (int i=0; i<node.getChildNodes().getLength(); i++) {
        //            addProcessingInstructionsTo(list, node.getChildNodes().item(i));
        //        }
        //    }
        //}

        //private void addTextNodesToRemoveAndTrim(List<Node> toRemove, Node node) {
        //    if (node is Text) {
        //        Text text = (Text)node;
        //        bool BUG_369394_IS_VALID = false;
        //        if (!BUG_369394_IS_VALID) {
        //            text.setData(text.getData().trim());
        //        } else {
        //            if (text.getData().trim().length() == 0) {
        //                text.setData("");
        //            }
        //        }
        //        if (text.getData().length() == 0) {
        //            toRemove.add(node);
        //        }
        //    }
        //    if (node.getChildNodes() != null) {
        //        for (int i=0; i<node.getChildNodes().getLength(); i++) {
        //            addTextNodesToRemoveAndTrim(toRemove, node.getChildNodes().item(i));
        //        }
        //    }
        //}

        //final Node toXml(String defaultNamespaceUri, String xml) throws org.xml.sax.SAXException {
        //    //    See ECMA357 10.3.1
        //    DocumentBuilder builder = null;
        //    try {
        //        String syntheticXml = "<parent xmlns=\"" + defaultNamespaceUri +
        //            "\">" + xml + "</parent>";
        //        builder = getDocumentBuilderFromPool(); 
        //        Document document = builder.parse( new org.xml.sax.InputSource(new java.io.StringReader(syntheticXml)) );
        //        if (ignoreProcessingInstructions) {
        //            List<Node> list = new java.util.ArrayList<Node>();
        //            addProcessingInstructionsTo(list, document);
        //            for (Node node: list) {
        //                node.getParentNode().removeChild(node);
        //            }
        //        }
        //        if (ignoreComments) {
        //            List<Node> list = new java.util.ArrayList<Node>();
        //            addCommentsTo(list, document);
        //            for (Node node: list) {
        //                node.getParentNode().removeChild(node);
        //            }
        //        }
        //        if (ignoreWhitespace) {
        //            //    Apparently JAXP setIgnoringElementContentWhitespace() has a different meaning, it appears from the Javadoc
        //            //    Refers to element-only content models, which means we would need to have a validating parser and DTD or schema
        //            //    so that it would know which whitespace to ignore.

        //            //    Instead we will try to delete it ourselves.
        //            List<Node> list = new java.util.ArrayList<Node>();
        //            addTextNodesToRemoveAndTrim(list, document);
        //            for (Node node: list) {
        //                node.getParentNode().removeChild(node);
        //            }
        //        }
        //        NodeList rv = document.getDocumentElement().getChildNodes();
        //        if (rv.getLength() > 1) {
        //            throw ScriptRuntime.constructError("SyntaxError", "XML objects may contain at most one node.");
        //        } else if (rv.getLength() == 0) {
        //            Node node = document.createTextNode("");
        //            return node;
        //        } else {
        //            Node node = rv.item(0);
        //            document.getDocumentElement().removeChild(node);
        //            return node;
        //        }
        //    } catch (java.io.IOException e) {
        //        throw new RuntimeException("Unreachable.");
        //    } catch (javax.xml.parsers.ParserConfigurationException e) {
        //        throw new RuntimeException(e);
        //    } finally {
        //        if (builder != null)
        //            returnDocumentBuilderToPool(builder);
        //    }
        //}

        //Document newDocument() {
        //    DocumentBuilder builder = null;
        //    try {
        //        //    TODO    Should this use XML settings?
        //        builder = getDocumentBuilderFromPool();
        //        return builder.newDocument();
        //    } catch (javax.xml.parsers.ParserConfigurationException ex) {
        //        //    TODO    How to handle these runtime errors?
        //        throw new RuntimeException(ex);
        //    } finally {
        //        if (builder != null)
        //            returnDocumentBuilderToPool(builder);
        //    }
        //}

        ////    TODO    Cannot remember what this is for, so whether it should use settings or not
        //private String toString(Node node) {
        //    javax.xml.transform.dom.DOMSource source = new javax.xml.transform.dom.DOMSource(node);
        //    java.io.StringWriter writer = new java.io.StringWriter();
        //    javax.xml.transform.stream.StreamResult result = new javax.xml.transform.stream.StreamResult(writer);
        //    try {
        //        javax.xml.transform.Transformer transformer = xform.newTransformer();
        //        transformer.setOutputProperty(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
        //        transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "no");
        //        transformer.setOutputProperty(javax.xml.transform.OutputKeys.METHOD, "xml");
        //        transformer.transform(source, result);
        //    } catch (javax.xml.transform.TransformerConfigurationException ex) {
        //        //    TODO    How to handle these runtime errors?
        //        throw new RuntimeException(ex);
        //    } catch (javax.xml.transform.TransformerException ex) {
        //        //    TODO    How to handle these runtime errors?
        //        throw new RuntimeException(ex);
        //    }
        //    return toXmlNewlines(writer.toString());
        //}

        //String escapeAttributeValue(Object value) {
        //    String text = ScriptRuntime.toString(value);

        //    if (text.length() == 0) return "";

        //    Document dom = newDocument();
        //    Element e = dom.createElement("a");
        //    e.setAttribute("b", text);
        //    String elementText = toString(e);
        //    int begin = elementText.indexOf('"');
        //    int end = elementText.lastIndexOf('"');
        //    return elementText.substring(begin+1,end);
        //}

        //String escapeTextValue(Object value) {
        //    if (value is XMLObjectImpl) {
        //        return ((XMLObjectImpl)value).toXMLString();
        //    }

        //    String text = ScriptRuntime.toString(value);

        //    if (text.length() == 0) return text;

        //    Document dom = newDocument();
        //    Element e = dom.createElement("a");
        //    e.setTextContent(text);
        //    String elementText = toString(e);

        //    int begin = elementText.indexOf('>') + 1;
        //    int end = elementText.lastIndexOf('<');
        //    return (begin < end) ? elementText.substring(begin, end) : "";
        //}

        //private String escapeElementValue(String s) {
        //    //    TODO    Check this
        //    return escapeTextValue(s);
        //}

        //private String elementToXmlString(Element element) {
        //    //    TODO    My goodness ECMA is complicated (see 10.2.1).  We'll try this first.
        //    Element copy = (Element)element.cloneNode(true);
        //    if (prettyPrint) {
        //        beautifyElement(copy, 0);
        //    }
        //    return toString(copy);
        //}

        // String ecmaToXmlString(Node node) {
        //    //    See ECMA 357 Section 10.2.1
        //    StringBuffer s = new StringBuffer();
        //    int indentLevel = 0;
        //    if (prettyPrint) {
        //        for (int i=0; i<indentLevel; i++) {
        //            s.Append(' ');
        //        }
        //    }
        //    if (node is Text) {
        //        String data = ((Text)node).getData();
        //        //    TODO Does Java trim() work same as XMLWhitespace?
        //        String v = (prettyPrint) ? data.trim() : data;
        //        s.Append(escapeElementValue(v));
        //        return s.toString();
        //    }
        //    if (node is Attr) {
        //        String value = ((Attr)node).getValue();
        //        s.Append(escapeAttributeValue(value));
        //        return s.toString();
        //    }
        //    if (node is Comment) {
        //        s.Append("<!--" + ((Comment)node).getNodeValue() + "-->");
        //        return s.toString();
        //    }
        //    if (node is ProcessingInstruction) {
        //        ProcessingInstruction pi = (ProcessingInstruction)node;
        //        s.Append("<?" + pi.getTarget() + " " + pi.getData() + "?>");
        //        return s.toString();
        //    }
        //    s.Append(elementToXmlString((Element)node));
        //    return s.toString();
        //}

        //private void beautifyElement(Element e, int indent) {
        //    StringBuilder s = new StringBuilder();
        //    s.Append('\n');
        //    for (int i=0; i<indent; i++) {
        //        s.Append(' ');
        //    }
        //    String afterContent = s.toString();
        //    for (int i=0; i<prettyIndent; i++) {
        //        s.Append(' ');
        //    }
        //    String beforeContent = s.toString();

        //    //    We "mark" all the nodes first; if we tried to do this loop otherwise, it would behave unexpectedly (the inserted nodes
        //    //    would contribute to the length and it might never terminate).
        //    IList<Node> toIndent = new List<Node>();
        //    bool indentChildren = false;
        //    for (int i=0; i<e.getChildNodes().getLength(); i++) {
        //        if (i == 1) indentChildren = true;
        //        if (e.getChildNodes().item(i) is Text) {
        //            toIndent.add(e.getChildNodes().item(i));
        //        } else {
        //            indentChildren = true;
        //            toIndent.add(e.getChildNodes().item(i));
        //        }
        //    }
        //    if (indentChildren) {
        //        for (int i=0; i<toIndent.size(); i++) {
        //            e.insertBefore(e.getOwnerDocument().createTextNode(beforeContent),
        //                    toIndent.get(i));
        //        }
        //    }
        //    NodeList nodes = e.getChildNodes();
        //    ArrayList<Element> list = new ArrayList<Element>();
        //    for (int i=0; i < nodes.getLength(); i++) {
        //        if (nodes.item(i) is Element) {
        //            list.add((Element)nodes.item(i));
        //        }
        //    }
        //    for (Element elem: list) {
        //        beautifyElement(elem, indent + prettyIndent);
        //    }
        //    if (indentChildren) {
        //        e.appendChild(e.getOwnerDocument().createTextNode(afterContent));
        //    }
        //}
    }// end for class
}
